/*******************************************************************************
 * Copyright (c) 2023-09-07 @author <a href="mailto:iffiff1@gmail.com">Tyler Chen</a>.
 * All rights reserved.
 *
 * Contributors:
 *     <a href="mailto:iffiff1@gmail.com">Tyler Chen</a> - initial API and implementation.
 ******************************************************************************/
package org.iff.util;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.List;

/**
 * 生成验证码图片，避免使用系统中的 font
 *
 * @author <a href="mailto:iffiff1@gmail.com">Tyler Chen</a>
 * @since 2023-09-07
 */
public class CaptchaUtil {
    /**
     * 字符高度为：18，打印时需要2倍，也就是：36，所以生成图像的高度最好要大于：36
     * http://patorjk.com/software/taag
     */
    private static final String chars = "" +
            "               AAA                ||||||| BBBBBBBBBBBBBBBBB    |||||||         CCCCCCCCCCCCC ||||||| DDDDDDDDDDDDD         ||||||| EEEEEEEEEEEEEEEEEEEEEE ||||||| FFFFFFFFFFFFFFFFFFFFFF |||||||         GGGGGGGGGGGGG ||||||| HHHHHHHHH     HHHHHHHHH ||||||| IIIIIIIIII |||||||           JJJJJJJJJJJ ||||||| KKKKKKKKK    KKKKKKK ||||||| LLLLLLLLLLL              ||||||| MMMMMMMM               MMMMMMMM ||||||| NNNNNNNN        NNNNNNNN |||||||      OOOOOOOOO      ||||||| PPPPPPPPPPPPPPPPP    |||||||      QQQQQQQQQ       ||||||| RRRRRRRRRRRRRRRRR    |||||||    SSSSSSSSSSSSSSS  ||||||| TTTTTTTTTTTTTTTTTTTTTTT ||||||| UUUUUUUU     UUUUUUUU ||||||| VVVVVVVV           VVVVVVVV ||||||| WWWWWWWW                           WWWWWWWW ||||||| XXXXXXX       XXXXXXX ||||||| YYYYYYY       YYYYYYY ||||||| ZZZZZZZZZZZZZZZZZZZ ||||||| \n" +
            "              A:::A               |:::::| B::::::::::::::::B   |:::::|      CCC::::::::::::C |:::::| D::::::::::::DDD      |:::::| E::::::::::::::::::::E |:::::| F::::::::::::::::::::F |:::::|      GGG::::::::::::G |:::::| H:::::::H     H:::::::H |:::::| I::::::::I |:::::|           J:::::::::J |:::::| K:::::::K    K:::::K |:::::| L:::::::::L              |:::::| M:::::::M             M:::::::M |:::::| N:::::::N       N::::::N |:::::|    OO:::::::::OO    |:::::| P::::::::::::::::P   |:::::|    QQ:::::::::QQ     |:::::| R::::::::::::::::R   |:::::|  SS:::::::::::::::S |:::::| T:::::::::::::::::::::T |:::::| U::::::U     U::::::U |:::::| V::::::V           V::::::V |:::::| W::::::W                           W::::::W |:::::| X:::::X       X:::::X |:::::| Y:::::Y       Y:::::Y |:::::| Z:::::::::::::::::Z |:::::| \n" +
            "             A:::::A              |:::::| B::::::BBBBBB:::::B  |:::::|    CC:::::::::::::::C |:::::| D:::::::::::::::DD    |:::::| E::::::::::::::::::::E |:::::| F::::::::::::::::::::F |:::::|    GG:::::::::::::::G |:::::| H:::::::H     H:::::::H |:::::| I::::::::I |:::::|           J:::::::::J |:::::| K:::::::K    K:::::K |:::::| L:::::::::L              |:::::| M::::::::M           M::::::::M |:::::| N::::::::N      N::::::N |:::::|  OO:::::::::::::OO  |:::::| P::::::PPPPPP:::::P  |:::::|  QQ:::::::::::::QQ   |:::::| R::::::RRRRRR:::::R  |:::::| S:::::SSSSSS::::::S |:::::| T:::::::::::::::::::::T |:::::| U::::::U     U::::::U |:::::| V::::::V           V::::::V |:::::| W::::::W                           W::::::W |:::::| X:::::X       X:::::X |:::::| Y:::::Y       Y:::::Y |:::::| Z:::::::::::::::::Z |:::::| \n" +
            "            A:::::::A             |:::::| BB:::::B     B:::::B |:::::|   C:::::CCCCCCCC::::C |:::::| DDD:::::DDDDD:::::D   |:::::| EE::::::EEEEEEEEE::::E |:::::| FF::::::FFFFFFFFF::::F |:::::|   G:::::GGGGGGGG::::G |:::::| HH::::::H     H::::::HH |:::::| II::::::II |:::::|           JJ:::::::JJ |:::::| K:::::::K   K::::::K |:::::| LL:::::::LL              |:::::| M:::::::::M         M:::::::::M |:::::| N:::::::::N     N::::::N |:::::| O:::::::OOO:::::::O |:::::| PP:::::P     P:::::P |:::::| Q:::::::QQQ:::::::Q  |:::::| RR:::::R     R:::::R |:::::| S:::::S     SSSSSSS |:::::| T:::::TT:::::::TT:::::T |:::::| UU:::::U     U:::::UU |:::::| V::::::V           V::::::V |:::::| W::::::W                           W::::::W |:::::| X::::::X     X::::::X |:::::| Y::::::Y     Y::::::Y |:::::| Z:::ZZZZZZZZ:::::Z  |:::::| \n" +
            "           A:::::::::A            |:::::|   B::::B     B:::::B |:::::|  C:::::C       CCCCCC |:::::|   D:::::D    D:::::D  |:::::|   E:::::E       EEEEEE |:::::|   F:::::F       FFFFFF |:::::|  G:::::G       GGGGGG |:::::|   H:::::H     H:::::H   |:::::|   I::::I   |:::::|             J:::::J   |:::::| KK::::::K  K:::::KKK |:::::|   L:::::L                |:::::| M::::::::::M       M::::::::::M |:::::| N::::::::::N    N::::::N |:::::| O::::::O   O::::::O |:::::|   P::::P     P:::::P |:::::| Q::::::O   Q::::::Q  |:::::|   R::::R     R:::::R |:::::| S:::::S             |:::::| TTTTTT  T:::::T  TTTTTT |:::::|  U:::::U     U:::::U  |:::::|  V:::::V           V:::::V  |:::::|  W:::::W           WWWWW           W:::::W  |:::::| XXX:::::X   X:::::XXX |:::::| YYY:::::Y   Y:::::YYY |:::::| ZZZZZ     Z:::::Z   |:::::| \n" +
            "          A:::::A:::::A           |:::::|   B::::B     B:::::B |:::::| C:::::C               |:::::|   D:::::D     D:::::D |:::::|   E:::::E              |:::::|   F:::::F              |:::::| G:::::G               |:::::|   H:::::H     H:::::H   |:::::|   I::::I   |:::::|             J:::::J   |:::::|   K:::::K K:::::K    |:::::|   L:::::L                |:::::| M:::::::::::M     M:::::::::::M |:::::| N:::::::::::N   N::::::N |:::::| O:::::O     O:::::O |:::::|   P::::P     P:::::P |:::::| Q:::::O     Q:::::Q  |:::::|   R::::R     R:::::R |:::::| S:::::S             |:::::|         T:::::T         |:::::|  U:::::D     D:::::U  |:::::|   V:::::V         V:::::V   |:::::|   W:::::W         W:::::W         W:::::W   |:::::|    X:::::X X:::::X    |:::::|    Y:::::Y Y:::::Y    |:::::|         Z:::::Z     |:::::| \n" +
            "         A:::::A A:::::A          |||||||   B::::BBBBBB:::::B  ||||||| C:::::C               |||||||   D:::::D     D:::::D |||||||   E::::::EEEEEEEEEE    |||||||   F::::::FFFFFFFFFF    ||||||| G:::::G               |||||||   H::::::HHHHH::::::H   |||||||   I::::I   |||||||             J:::::J   |||||||   K::::::K:::::K     |||||||   L:::::L                ||||||| M:::::::M::::M   M::::M:::::::M ||||||| N:::::::N::::N  N::::::N ||||||| O:::::O     O:::::O |||||||   P::::PPPPPP:::::P  ||||||| Q:::::O     Q:::::Q  |||||||   R::::RRRRRR:::::R  |||||||  S::::SSSS          |||||||         T:::::T         |||||||  U:::::D     D:::::U  |||||||    V:::::V       V:::::V    |||||||    W:::::W       W:::::::W       W:::::W    |||||||     X:::::X:::::X     |||||||     Y:::::Y:::::Y     |||||||        Z:::::Z      ||||||| \n" +
            "        A:::::A   A:::::A                   B:::::::::::::BB           C:::::C                         D:::::D     D:::::D           E:::::::::::::::E              F:::::::::::::::F            G:::::G    GGGGGGGGGG           H:::::::::::::::::H             I::::I                       J:::::j             K:::::::::::K                L:::::L                        M::::::M M::::M M::::M M::::::M         N::::::N N::::N N::::::N         O:::::O     O:::::O           P:::::::::::::PP           Q:::::O     Q:::::Q            R:::::::::::::RR             SS::::::SSSSS                     T:::::T                  U:::::D     D:::::U              V:::::V     V:::::V                 W:::::W     W:::::::::W     W:::::W                  X:::::::::X                   Y:::::::::Y                    Z:::::Z               \n" +
            "       A:::::A     A:::::A                  B::::BBBBBB:::::B          C:::::C                         D:::::D     D:::::D           E:::::::::::::::E              F:::::::::::::::F            G:::::G    G::::::::G           H:::::::::::::::::H             I::::I                       J:::::J             K:::::::::::K                L:::::L                        M::::::M  M::::M::::M  M::::::M         N::::::N  N::::N:::::::N         O:::::O     O:::::O           P::::PPPPPPPPP             Q:::::O     Q:::::Q            R::::RRRRRR:::::R              SSS::::::::SS                   T:::::T                  U:::::D     D:::::U               V:::::V   V:::::V                   W:::::W   W:::::W:::::W   W:::::W                   X:::::::::X                    Y:::::::Y                    Z:::::Z                \n" +
            "      A:::::AAAAAAAAA:::::A       |||||||   B::::B     B:::::B ||||||| C:::::C               |||||||   D:::::D     D:::::D |||||||   E::::::EEEEEEEEEE    |||||||   F::::::FFFFFFFFFF    ||||||| G:::::G    GGGGG::::G |||||||   H::::::HHHHH::::::H   |||||||   I::::I   ||||||| JJJJJJJ     J:::::J   |||||||   K::::::K:::::K     |||||||   L:::::L                ||||||| M::::::M   M:::::::M   M::::::M ||||||| N::::::N   N:::::::::::N ||||||| O:::::O     O:::::O |||||||   P::::P             ||||||| Q:::::O     Q:::::Q  |||||||   R::::R     R:::::R |||||||        SSSSSS::::S  |||||||         T:::::T         |||||||  U:::::D     D:::::U  |||||||       V:::::V V:::::V       |||||||       W:::::W W:::::W W:::::W W:::::W       |||||||     X:::::X:::::X     |||||||        Y:::::Y        |||||||     Z:::::Z         ||||||| \n" +
            "     A:::::::::::::::::::::A      |:::::|   B::::B     B:::::B |:::::| C:::::C               |:::::|   D:::::D     D:::::D |:::::|   E:::::E              |:::::|   F:::::F              |:::::| G:::::G        G::::G |:::::|   H:::::H     H:::::H   |:::::|   I::::I   |:::::| J:::::J     J:::::J   |:::::|   K:::::K K:::::K    |:::::|   L:::::L                |:::::| M::::::M    M:::::M    M::::::M |:::::| N::::::N    N::::::::::N |:::::| O:::::O     O:::::O |:::::|   P::::P             |:::::| Q:::::O  QQQQ:::::Q  |:::::|   R::::R     R:::::R |:::::|             S:::::S |:::::|         T:::::T         |:::::|  U:::::D     D:::::U  |:::::|        V:::::V:::::V        |:::::|        W:::::W:::::W   W:::::W:::::W        |:::::|    X:::::X X:::::X    |:::::|        Y:::::Y        |:::::|    Z:::::Z          |:::::| \n" +
            "    A:::::AAAAAAAAAAAAA:::::A     |:::::|   B::::B     B:::::B |:::::|  C:::::C       CCCCCC |:::::|   D:::::D    D:::::D  |:::::|   E:::::E       EEEEEE |:::::|   F:::::F              |:::::|  G:::::G       G::::G |:::::|   H:::::H     H:::::H   |:::::|   I::::I   |:::::| J::::::J   J::::::J   |:::::| KK::::::K  K:::::KKK |:::::|   L:::::L         LLLLLL |:::::| M::::::M     MMMMM     M::::::M |:::::| N::::::N     N:::::::::N |:::::| O::::::O   O::::::O |:::::|   P::::P             |:::::| Q::::::O Q::::::::Q  |:::::|   R::::R     R:::::R |:::::|             S:::::S |:::::|         T:::::T         |:::::|  U::::::U   U::::::U  |:::::|         V:::::::::V         |:::::|         W:::::::::W     W:::::::::W         |:::::| XXX:::::X   X:::::XXX |:::::|        Y:::::Y        |:::::| ZZZ:::::Z     ZZZZZ |:::::| \n" +
            "   A:::::A             A:::::A    |:::::| BB:::::BBBBBB::::::B |:::::|   C:::::CCCCCCCC::::C |:::::| DDD:::::DDDDD:::::D   |:::::| EE::::::EEEEEEEE:::::E |:::::| FF:::::::FF            |:::::|   G:::::GGGGGGGG::::G |:::::| HH::::::H     H::::::HH |:::::| II::::::II |:::::| J:::::::JJJ:::::::J   |:::::| K:::::::K   K::::::K |:::::| LL:::::::LLLLLLLLL:::::L |:::::| M::::::M               M::::::M |:::::| N::::::N      N::::::::N |:::::| O:::::::OOO:::::::O |:::::| PP::::::PP           |:::::| Q:::::::QQ::::::::Q  |:::::| RR:::::R     R:::::R |:::::| SSSSSSS     S:::::S |:::::|       TT:::::::TT       |:::::|  U:::::::UUU:::::::U  |:::::|          V:::::::V          |:::::|          W:::::::W       W:::::::W          |:::::| X::::::X     X::::::X |:::::|        Y:::::Y        |:::::| Z::::::ZZZZZZZZ:::Z |:::::| \n" +
            "  A:::::A               A:::::A   |:::::| B:::::::::::::::::B  |:::::|    CC:::::::::::::::C |:::::| D:::::::::::::::DD    |:::::| E::::::::::::::::::::E |:::::| F::::::::FF            |:::::|    GG:::::::::::::::G |:::::| H:::::::H     H:::::::H |:::::| I::::::::I |:::::|  JJ:::::::::::::JJ    |:::::| K:::::::K    K:::::K |:::::| L::::::::::::::::::::::L |:::::| M::::::M               M::::::M |:::::| N::::::N       N:::::::N |:::::|  OO:::::::::::::OO  |:::::| P::::::::P           |:::::|  QQ::::::::::::::Q   |:::::| R::::::R     R:::::R |:::::| S::::::SSSSSS:::::S |:::::|       T:::::::::T       |:::::|   UU:::::::::::::UU   |:::::|           V:::::V           |:::::|           W:::::W         W:::::W           |:::::| X:::::X       X:::::X |:::::|     YYYY:::::YYYY     |:::::| Z:::::::::::::::::Z |:::::| \n" +
            " A:::::A                 A:::::A  |:::::| B::::::::::::::::B   |:::::|      CCC::::::::::::C |:::::| D::::::::::::DDD      |:::::| E::::::::::::::::::::E |:::::| F::::::::FF            |:::::|      GGG::::::GGG:::G |:::::| H:::::::H     H:::::::H |:::::| I::::::::I |:::::|    JJ:::::::::JJ      |:::::| K:::::::K    K:::::K |:::::| L::::::::::::::::::::::L |:::::| M::::::M               M::::::M |:::::| N::::::N        N::::::N |:::::|    OO:::::::::OO    |:::::| P::::::::P           |:::::|    QQ:::::::::::Q    |:::::| R::::::R     R:::::R |:::::| S:::::::::::::::SS  |:::::|       T:::::::::T       |:::::|     UU:::::::::UU     |:::::|            V:::V            |:::::|            W:::W           W:::W            |:::::| X:::::X       X:::::X |:::::|     Y:::::::::::Y     |:::::| Z:::::::::::::::::Z |:::::| \n" +
            "AAAAAAA                   AAAAAAA ||||||| BBBBBBBBBBBBBBBBB    |||||||         CCCCCCCCCCCCC ||||||| DDDDDDDDDDDDD         ||||||| EEEEEEEEEEEEEEEEEEEEEE ||||||| FFFFFFFFFFF            |||||||         GGGGGG   GGGG ||||||| HHHHHHHHH     HHHHHHHHH ||||||| IIIIIIIIII |||||||      JJJJJJJJJ        ||||||| KKKKKKKKK    KKKKKKK ||||||| LLLLLLLLLLLLLLLLLLLLLLLL ||||||| MMMMMMMM               MMMMMMMM ||||||| NNNNNNNN         NNNNNNN |||||||      OOOOOOOOO      ||||||| PPPPPPPPPP           |||||||      QQQQQQQQ::::QQ  ||||||| RRRRRRRR     RRRRRRR |||||||  SSSSSSSSSSSSSSS    |||||||       TTTTTTTTTTT       |||||||       UUUUUUUUU       |||||||             VVV             |||||||             WWW             WWW             ||||||| XXXXXXX       XXXXXXX |||||||     YYYYYYYYYYYYY     ||||||| ZZZZZZZZZZZZZZZZZZZ ||||||| \n" +
            "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             Q:::::Q                                                                                                                                                                                                                                                                                                                \n" +
            "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              QQQQQQ                                                                                                                                                                                                                                                                                                                ";
    private static Map<Character, char[][]> CHAR_MAP = new HashMap<>();

    static {
        readChars();
    }

    /**
     * 分割所有字符图形
     */
    static Map<Character, char[][]> readChars() {
        if (!CHAR_MAP.isEmpty()) {
            return CHAR_MAP;
        }
        Map<Character, char[][]> map = new HashMap<>();
        //Split chars
        String[] lines = chars.split("\n");
        String firstLine = lines[0];
        int startPos = 0, endPos = 0;
        while (endPos >= 0) {
            //find end pos
            endPos = firstLine.indexOf(" |", endPos);
            if (endPos < 0) {
                break;
            }
            String tmp = firstLine.substring(startPos, endPos).trim();
            //read char art
            List<char[]> list = new ArrayList<>();
            for (String line : lines) {
                list.add(line.substring(startPos, endPos).replaceAll("[^ ]", ".").toCharArray());
            }
            map.put(tmp.charAt(0), list.toArray(new char[list.size()][list.get(0).length]));
            //find start pos
            startPos = firstLine.indexOf("| ", endPos + 2);
            if (startPos < 0) {
                break;
            }
            endPos = startPos = startPos + 2;
        }
        CHAR_MAP.putAll(map);
        return map;
    }

    public static void main(String[] args) throws Exception {
        Map<Character, char[][]> charMap = readChars();
        charMap.forEach((k, v) -> {
            System.out.println("==" + k + "==");
            for (char[] line : v) {
                System.out.println(new String(line));
            }
        });
        String chars = randomChar(4);
        FileOutputStream fos = new FileOutputStream("/Users/zhaochen/Desktop/test.png");
        captcha(chars.length() * 42, chars.length() > 4 ? (chars.length() * 42 / 3) : 56, chars, fos);
        fos.close();
        System.out.println(chars);
    }

    /**
     * 生成 4 位验证码，默认宽高比 3:1
     *
     * @param os 图片输出
     * @return 4 位验证码
     */
    public static String captcha4(OutputStream os) {
        try {
            String chars = randomChar(4);
            captcha(chars.length() * 42, chars.length() > 4 ? (chars.length() * 42 / 3) : 56, chars, os);
            return chars;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 随机生成字符串：A-Z
     *
     * @param count 字符个数
     * @return 生成的字符串
     */
    public static String randomChar(int count) {
        if (count < 1) {
            return "";
        }
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < count; i++) {
            sb.append((char) (((int) 'A') + random.nextInt(CHAR_MAP.size())));
        }
        return sb.toString();
    }

    /**
     * 生成验证码图片
     *
     * @param width  图片宽度
     * @param height 图片高度
     * @param chars  验证码字符串
     * @param os     输出流，如果为 null 则返回 ByteArrayOutputStream
     * @return 如果 os 为 null 则返回 ByteArrayOutputStream
     * @throws Exception
     */
    public static OutputStream captcha(int width, int height, String chars, OutputStream os) throws Exception {
        // 在内存中创建图象
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Random random = new Random();
        int black = Color.BLACK.getRGB();
        int gray = Color.GRAY.getRGB();
        int red = Color.RED.getRGB();
        int green = Color.GREEN.getRGB();
        int white = Color.WHITE.getRGB();
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, white);
                int rgb = random.nextInt();
                if (rgb % 9 == 0) {
                    image.setRGB(x, y, black);
                }
                if (rgb % 4 == 0) {
                    image.setRGB(x, y, rgb);
                }
            }
        }
        int startX = random.nextInt(10);
        for (char c : chars.toUpperCase().toCharArray()) {
            int rotate = random.nextInt(10) % 3;
            int heightOff = random.nextInt(height / 2 - 36 / 2);
            rotate = rotate == 2 ? -1 : (rotate == 1 ? 1 : rotate);
            int color = rotate == 1 ? green : (rotate == 0 ? red : black);
            startX = drawChar(image, c, gray, color,
                    startX + (rotate < 0 ? 18 : 0),
                    (height / 2 - 18) + (heightOff % 3 == 0 ? 0 : (heightOff % 3 == 1 ? heightOff : (-1 * heightOff))),
                    rotate);
        }
        os = os == null ? new ByteArrayOutputStream() : os;
        // 输出图象到页面
        ImageIO.write(image, "PNG", os);
        return os;
    }

    /**
     * @param image
     * @param c      字符：A-Z
     * @param rgb0   颜色
     * @param rgb1   颜色
     * @param offX   X轴偏移量
     * @param offY   Y 轴偏移量
     * @param rotate 倾斜度范围值：[-1,0,1]
     * @return 返回当前画图的最大 X 值用于续下一图，0为没有画图
     */
    static int drawChar(BufferedImage image, char c, int rgb0, int rgb1, int offX, int offY, int rotate) {
        int w = image.getWidth();
        int h = image.getHeight();
        int rgbUp = rgb0, rgbDown = rgb1;
        int maxX = 0;
        char[][] charArr = CHAR_MAP.get(c);
        if (charArr == null) {
            return maxX;
        }
        if (((int) c) % 2 == 0) {
            rgbUp = rgb1;
            rgbDown = rgb0;
        }
        for (int y = 0; y < charArr.length; y++) {//高度：Y
            char[] line = charArr[y];
            for (int x = 0; x < line.length; x++) {//宽度：X
                int posX = x + offX + y * rotate, posY = y * 2 + offY;
                maxX = Math.max(maxX, posX);
                if (line[x] == '.' && posX > -1 && posY > -1 && posX < w && posY < h) {
                    image.setRGB(posX, posY, y * 2 < charArr.length ? rgbUp : rgbDown);
                    if (posY + 1 < h) {//减少这一行可以让字符更难识别
                        image.setRGB(posX, posY + 1, y * 2 < charArr.length ? rgbUp : rgbDown);
                    }
                }
            }
        }
        return maxX;
    }
}
